
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

/**
 * This class represents a connection between the server and a client. It handles incoming and
 * outgoing communications between the two peers.
 * 
 * @author Alexandre Dos Santos Coelho
 */
public class Client extends Thread {

  private String machineName; // Client machine name
  private String machineType; // Client machine model
  private Socket socket; // Communication socket
  private Server server; // Link to server (to transmit received measures)
  private BufferedReader in; // Incomming communication channel
  private PrintStream out; // Outgoing communication channel
  private boolean running = true; // Determines if connection has to be maintained

  /**
   * Constructor. Creates client connection and starts thread.
   * @param socket Communication socket
   * @param server Attached server
   */
  public Client(Socket socket, Server server) {
    this.socket = socket;
    this.server = server;

    try {
      // Creating incomming / outgoing communication channels
      in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      out = new PrintStream(socket.getOutputStream());
    } catch (IOException ex) {
      System.err.println(Logger.getTime() + "Impossible to open input/output channels");
    }

    this.start();
  }

  /**
   * Sends and order given by the network administrators to the client
   *
   * @param order Order to transmit
   */
  public void sendOrder(Order order) {
    if (out != null) {
      System.out.println(Logger.getTime() + "Sent order " + order.getOrder() + " to " + order.getMachineId());
      out.println("order#" + order.getOrder());
    }
  }

  /**
   * Thread run method. First we expect user to authenticate with its hostname. Then we
   * receive measures and we confirm good reception of these.
   */
  public void run() {
    String line;

    while (running) {
      String[] message = null;
      try {
        // Client authentication
        do {
          line = in.readLine();
          if (line != null) {
            message = line.split("#"); // splitting message
          } else {
            running = false;
            break;
          }
        } while (!message[0].equals("hello")); // Waiting to receive machine name

        // Authenticated successfully
        if (message != null && message.length > 2) {
          machineName = message[1];
          setMachineType(message[2]);
          System.out.println(Logger.getTime() + "New machine authenticated: " + getMachineName() + "(" +
                  getMachineType() + ")");
          server.addClient(machineName, this);
        }

        // Receiving measures
        while (running) {
          line = in.readLine();

          if (line != null) {
            message = line.split("#"); // Splitting message
            // Client terminates
            if (message[0].equals("end")) {
              System.out.println(Logger.getTime() + "Connection with " + getMachineName() + " terminated");
              running = false;
            } else if (message[0].equals("measure")) { // Storing measure
              server.getDataManager().storeMeasure(new Measure(message, getMachineName(), getMachineType()));
              out.println("measure#ok#" + message[5]); // Sending confirmation message
            }
          } else { // Read line is null, client is disconnected (e.g. crash)
            running = false;
          }
        }

      } catch (IOException ex) {
        System.err.println(Logger.getTime() + "Connection with client " + getMachineName() + " has been lost");
        running = false;
      }
    }

    try {
      running = false;
      socket.close();
      System.out.println(Logger.getTime() + "Machine " + getMachineName() + " disconnected");
      server.removeClient(this);
    } catch (IOException ex) {
    }
  }

  /**
   * @return the machineName
   */
  public String getMachineName() {
    return machineName;
  }

  /**
   * @return the machineType
   */
  public String getMachineType() {
    return machineType;
  }

  /**
   * @param machineType the machineType to set
   */
  public void setMachineType(String machineType) {
    this.machineType = machineType;
  }
}
